# Psnuffle password sniffer add-on class for ftp
# part of psnuffle sniffer auxiliary module
#
# When db is available reports go into db
# Also incorrect credentials are sniffed but marked
# as unsuccessful logins... (Typos are common :-) )
#

class SnifferFTP < BaseProtocolParser

  def register_sigs
    self.sigs = {
      :banner		=> /^(220\s*[^\r\n]+)/i,
      :user		=> /^USER\s+([^\s]+)/i,
      :pass		=> /^PASS\s+([^\s]+)/i,
      :login_pass => /^(230\s*[^\n]+)/i,
      :login_fail => /^(5\d\d\s*[^\n]+)/i,
      :bye      => /^221/
    }
  end

  def parse(pkt)
    # We want to return immediatly if we do not have a packet which is handled by us
    return unless pkt.is_tcp?
    return if (pkt.tcp_sport != 21 and pkt.tcp_dport != 21)
    s = find_session((pkt.tcp_sport == 21) ? get_session_src(pkt) : get_session_dst(pkt))
    s[:sname] ||= "ftp"

    self.sigs.each_key do |k|
      # There is only one pattern per run to test
      matched = nil
      matches = nil

      if(pkt.payload =~ self.sigs[k])
        matched = k
        matches = $1
      end

      case matched

      when :login_fail
        if(s[:user] and s[:pass])
          report_auth_info(s.merge({:active => false}))
          print_status("Failed FTP Login: #{s[:session]} >> #{s[:user]} / #{s[:pass]}")

          s[:pass] = ""
          return
        end

      when :login_pass
        if(s[:user] and s[:pass])
          report_auth_info(s)
          print_status("Successful FTP Login: #{s[:session]} >> #{s[:user]} / #{s[:pass]}")
          # Remove it form the session objects so freeup memory
          sessions.delete(s[:session])
          return
        end

      when :banner
        # Because some ftp server send multiple banner we take only the first one and ignore the rest
        if not (s[:info])
          s[:info] = matches
          report_service(s)
        end

      when :bye
        sessions.delete(s[:session])

      when nil
        # No matches, no saved state
      else
        sessions[s[:session]].merge!({k => matches})
      end # end case matched

    end # end of each_key
  end # end of parse
end

